package v1

import (
	"errors"
	"net/http"
	"net/url"
	"path/filepath"
	"strconv"
	"strings"

	"github.com/hay-kot/httpkit/errchain"
	"github.com/hay-kot/httpkit/server"
	"github.com/rs/zerolog/log"
	"github.com/sysadminsmedia/homebox/backend/internal/core/services"
	"github.com/sysadminsmedia/homebox/backend/internal/data/ent/attachment"
	"github.com/sysadminsmedia/homebox/backend/internal/data/repo"
	"github.com/sysadminsmedia/homebox/backend/internal/sys/validate"

	"gocloud.dev/blob"
	_ "gocloud.dev/blob/azureblob"
	_ "gocloud.dev/blob/fileblob"
	_ "gocloud.dev/blob/gcsblob"
	_ "gocloud.dev/blob/memblob"
	_ "gocloud.dev/blob/s3blob"
)

// isImageFile 判断文件类型是否为图片
func isImageFile(contentType string) bool {
	imageTypes := []string{
		"image/jpeg",
		"image/jpg",
		"image/png",
		"image/gif",
		"image/bmp",
		"image/tiff",
		"image/webp",
		"image/svg+xml",
	}
	for _, t := range imageTypes {
		if contentType == t {
			return true
		}
	}
	return false
}

type (
	ItemAttachmentToken struct {
		Token string `json:"token"`
	}
)

// HandleItemAttachmentCreate godocs
//
//	@Summary	Create Item Attachment
//	@Tags		Items Attachments
//	@Accept		multipart/form-data
//	@Produce	json
//	@Param		id		path		string	true	"Item ID"
//	@Param		file	formData	file	true	"File attachment"
//	@Param		type	formData	string	false	"Type of file"
//	@Param		primary	formData	bool	false	"Is this the primary attachment"
//	@Param		name	formData	string	true	"name of the file including extension"
//	@Success	200		{object}	repo.ItemOut
//	@Failure	422		{object}	validate.ErrorResponse
//	@Router		/v1/items/{id}/attachments [POST]
//	@Security	Bearer
func (ctrl *V1Controller) HandleItemAttachmentCreate() errchain.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) error {
		err := r.ParseMultipartForm(ctrl.maxUploadSize << 20)
		if err != nil {
			log.Err(err).Msg("failed to parse multipart form")
			return validate.NewRequestError(errors.New("failed to parse multipart form"), http.StatusBadRequest)
		}

		errs := validate.NewFieldErrors()

		file, _, err := r.FormFile("file")
		if err != nil {
			switch {
			case errors.Is(err, http.ErrMissingFile):
				log.Debug().Msg("file for attachment is missing")
				errs = errs.Append("file", "file is required")
			default:
				log.Err(err).Msg("failed to get file from form")
				return validate.NewRequestError(err, http.StatusInternalServerError)
			}
		}

		attachmentName := r.FormValue("name")
		if attachmentName == "" {
			log.Debug().Msg("failed to get name from form")
			errs = errs.Append("name", "name is required")
		}

		if !errs.Nil() {
			return server.JSON(w, http.StatusUnprocessableEntity, errs)
		}

		attachmentType := r.FormValue("type")
		if attachmentType == "" {
			// Attempt to auto-detect the type of the file
			ext := filepath.Ext(attachmentName)

			switch strings.ToLower(ext) {
			case ".jpg", ".jpeg", ".png", ".webp", ".gif", ".bmp", ".tiff", ".avif", ".ico", ".heic", ".jxl":
				attachmentType = attachment.TypePhoto.String()
			default:
				attachmentType = attachment.TypeAttachment.String()
			}
		}

		primary, err := strconv.ParseBool(r.FormValue("primary"))
		if err != nil {
			log.Debug().Msg("failed to parse primary from form")
			primary = false
		}

		id, err := ctrl.routeID(r)
		if err != nil {
			return err
		}

		ctx := services.NewContext(r.Context())

		item, err := ctrl.svc.Items.AttachmentAdd(
			ctx,
			id,
			attachmentName,
			attachment.Type(attachmentType),
			primary,
			file,
		)
		if err != nil {
			log.Err(err).Msg("failed to add attachment")
			return validate.NewRequestError(err, http.StatusInternalServerError)
		}

		return server.JSON(w, http.StatusCreated, item)
	}
}

// HandleItemAttachmentGet godocs
//
//	@Summary	Get Item Attachment
//	@Tags		Items Attachments
//	@Produce	application/octet-stream
//	@Param		id				path		string	true	"Item ID"
//	@Param		attachment_id	path		string	true	"Attachment ID"
//	@Success	200				{object}	ItemAttachmentToken
//	@Router		/v1/items/{id}/attachments/{attachment_id} [GET]
//	@Security	Bearer
func (ctrl *V1Controller) HandleItemAttachmentGet() errchain.HandlerFunc {
	return ctrl.handleItemAttachmentsHandler
}

// HandleItemAttachmentDelete godocs
//
//	@Summary	Delete Item Attachment
//	@Tags		Items Attachments
//	@Param		id				path	string	true	"Item ID"
//	@Param		attachment_id	path	string	true	"Attachment ID"
//	@Success	204
//	@Router		/v1/items/{id}/attachments/{attachment_id} [DELETE]
//	@Security	Bearer
func (ctrl *V1Controller) HandleItemAttachmentDelete() errchain.HandlerFunc {
	return ctrl.handleItemAttachmentsHandler
}

// HandleItemAttachmentUpdate godocs
//
//	@Summary	Update Item Attachment
//	@Tags		Items Attachments
//	@Param		id			path		string						true	"Item ID"
//	@Param		attachment_id	path		string						true	"Attachment ID"
//	@Param		payload			body		repo.ItemAttachmentUpdate	true	"Attachment Update"
//	@Success	200				{object}	repo.ItemOut
//	@Router		/v1/items/{id}/attachments/{attachment_id} [PUT]
//	@Security	Bearer
func (ctrl *V1Controller) HandleItemAttachmentUpdate() errchain.HandlerFunc {
	return ctrl.handleItemAttachmentsHandler
}

// HandleItemAttachmentCreateThumbnail godocs
//
//	@Summary	Create thumbnail for item attachment if not exists
//
//	@Tags		Items Attachments
//
//	@Param		id			path		string	true	"Item ID"
//	@Param		attachment_id	path		string	true	"Attachment ID"
//
//	@Success	200				{object}	repo.ItemOut
//
//	@Router		/v1/items/{id}/attachments/{attachment_id}/thumbnail [POST]
//	@Security	Bearer
func (ctrl *V1Controller) HandleItemAttachmentCreateThumbnail() errchain.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) error {
		_, err := ctrl.routeID(r)
		if err != nil {
			return err
		}

		attachmentID, err := ctrl.routeUUID(r, "attachment_id")
		if err != nil {
			return err
		}

		ctx := services.NewContext(r.Context())

		// 获取附件信息
		attachment, err := ctrl.repo.Attachments.Get(ctx, ctx.GID, attachmentID)
		if err != nil {
			log.Err(err).Msg("failed to get attachment")
			return validate.NewRequestError(err, http.StatusNotFound)
		}

		// 检查是否已存在缩略图 - 通过查询数据库中是否存在关联的缩略图附件
		hasThumbnail, err := attachment.QueryThumbnail().Exist(ctx)
		if err != nil {
			log.Err(err).Msg("failed to check thumbnail existence")
			return validate.NewRequestError(err, http.StatusInternalServerError)
		}
		if hasThumbnail {
			// 缩略图已存在，返回成功状态
			return server.JSON(w, http.StatusOK, map[string]string{"status": "thumbnail already exists"})
		}

		// 检查是否为图片类型
		if !isImageFile(attachment.MimeType) {
			return validate.NewRequestError(errors.New("attachment is not an image"), http.StatusBadRequest)
		}

		// 创建缩略图
		err = ctrl.repo.Attachments.CreateThumbnail(ctx, ctx.GID, attachmentID, attachment.Title, attachment.Path)
		if err != nil {
			log.Err(err).Msg("failed to create thumbnail")
			return validate.NewRequestError(err, http.StatusInternalServerError)
		}

		// 返回成功状态
		return server.JSON(w, http.StatusOK, map[string]string{"status": "thumbnail created"})
	}
}

// HandleItemAttachmentThumbnail godocs
//
//	@Summary	Get thumbnail for item attachment (create if not exists)
//
//	@Tags		Items Attachments
//
//	@Produce	image/webp
//	@Param		id			path		string	true	"Item ID"
//	@Param		attachment_id	path		string	true	"Attachment ID"
//
//	@Success	200				{file}		binary	"Thumbnail image"
//	@Failure	404				{object}		ErrorResponse
//	@Failure	500				{object}		ErrorResponse
//
//	@Router		/v1/items/{id}/attachments/{attachment_id}/thumbnail [GET]
//	@Security	Bearer
func (ctrl *V1Controller) HandleItemAttachmentThumbnail() errchain.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) error {
		itemID, err := ctrl.routeID(r)
		if err != nil {
			return err
		}

		attachmentID, err := ctrl.routeUUID(r, "attachment_id")
		if err != nil {
			return err
		}

		ctx := services.NewContext(r.Context())

		// 获取附件信息
		attachment, err := ctrl.repo.Attachments.Get(ctx, ctx.GID, attachmentID)
		if err != nil {
			log.Err(err).Msg("failed to get attachment")
			return validate.NewRequestError(err, http.StatusNotFound)
		}

		// 检查是否为图片类型
		if !isImageFile(attachment.MimeType) {
			return validate.NewRequestError(errors.New("attachment is not an image"), http.StatusBadRequest)
		}

		// 检查是否已存在缩略图
		hasThumbnail, err := attachment.QueryThumbnail().Exist(ctx)
		if err != nil {
			log.Err(err).Msg("failed to check thumbnail existence")
			return validate.NewRequestError(err, http.StatusInternalServerError)
		}

		var thumbnailAttachment *repo.ItemAttachment

		if hasThumbnail {
			// 缩略图已存在，直接获取
			thumbnailEnt, err := attachment.QueryThumbnail().Only(ctx)
			if err != nil {
				log.Err(err).Msg("failed to get existing thumbnail")
				return validate.NewRequestError(err, http.StatusInternalServerError)
			}
			attachmentStruct := repo.ToItemAttachment(thumbnailEnt)
			thumbnailAttachment = &attachmentStruct
		} else {
			// 缩略图不存在，创建缩略图
			err = ctrl.repo.Attachments.CreateThumbnail(ctx, ctx.GID, attachmentID, attachment.Title, attachment.Path)
			if err != nil {
				log.Err(err).Msg("failed to create thumbnail")
				return validate.NewRequestError(err, http.StatusInternalServerError)
			}

			// 重新获取附件信息以获取新创建的缩略图
			attachment, err = ctrl.repo.Attachments.Get(ctx, ctx.GID, attachmentID)
			if err != nil {
				log.Err(err).Msg("failed to get attachment after thumbnail creation")
				return validate.NewRequestError(err, http.StatusInternalServerError)
			}

			thumbnailEnt, err := attachment.QueryThumbnail().Only(ctx)
			if err != nil {
				log.Err(err).Msg("failed to get newly created thumbnail")
				return validate.NewRequestError(err, http.StatusInternalServerError)
			}
			attachmentStruct := repo.ToItemAttachment(thumbnailEnt)
			thumbnailAttachment = &attachmentStruct
		}

		// 获取缩略图文件
		bucket, err := blob.OpenBucket(ctx, ctrl.repo.Attachments.GetConnString())
		if err != nil {
			log.Err(err).Msg("failed to open bucket")
			return validate.NewRequestError(err, http.StatusInternalServerError)
		}
		defer bucket.Close()

		// 尝试读取缩略图文件，如果失败则自动重新生成
		file, err := bucket.NewReader(ctx, thumbnailAttachment.Path, nil)
		if err != nil {
			log.Warn().Err(err).Str("thumbnailPath", thumbnailAttachment.Path).Msg("缩略图文件不存在，开始自动重新生成")
			
			// 删除无效的缩略图数据库记录
			if hasThumbnail {
				thumbnailEnt, err := attachment.QueryThumbnail().Only(ctx)
				if err == nil {
					err = ctrl.repo.Attachments.Delete(ctx, ctx.GID, itemID, thumbnailEnt.ID)
					if err != nil {
						log.Err(err).Msg("failed to delete invalid thumbnail record")
					}
				}
			}
			
			// 重新生成缩略图
			err = ctrl.repo.Attachments.CreateThumbnail(ctx, ctx.GID, attachmentID, attachment.Title, attachment.Path)
			if err != nil {
				log.Err(err).Msg("failed to recreate thumbnail")
				return validate.NewRequestError(err, http.StatusInternalServerError)
			}
			
			// 重新获取附件信息以获取新创建的缩略图
			attachment, err = ctrl.repo.Attachments.Get(ctx, ctx.GID, attachmentID)
			if err != nil {
				log.Err(err).Msg("failed to get attachment after thumbnail recreation")
				return validate.NewRequestError(err, http.StatusInternalServerError)
			}
			
			thumbnailEnt, err := attachment.QueryThumbnail().Only(ctx)
			if err != nil {
				log.Err(err).Msg("failed to get newly recreated thumbnail")
				return validate.NewRequestError(err, http.StatusInternalServerError)
			}
			attachmentStruct := repo.ToItemAttachment(thumbnailEnt)
			thumbnailAttachment = &attachmentStruct
			
			// 再次尝试读取缩略图文件
			file, err = bucket.NewReader(ctx, thumbnailAttachment.Path, nil)
			if err != nil {
				log.Err(err).Msg("failed to open recreated thumbnail file")
				return validate.NewRequestError(err, http.StatusInternalServerError)
			}
			
			log.Info().Str("thumbnailPath", thumbnailAttachment.Path).Msg("缩略图重新生成成功")
		}
		defer file.Close()

		// 设置响应头
		w.Header().Set("Content-Type", "image/webp")
		w.Header().Set("Cache-Control", "public, max-age=86400") // 缓存24小时
		http.ServeContent(w, r, thumbnailAttachment.Title, thumbnailAttachment.UpdatedAt, file)
		return nil
	}
}

func (ctrl *V1Controller) handleItemAttachmentsHandler(w http.ResponseWriter, r *http.Request) error {
	ID, err := ctrl.routeID(r)
	if err != nil {
		return err
	}

	attachmentID, err := ctrl.routeUUID(r, "attachment_id")
	if err != nil {
		return err
	}

	ctx := services.NewContext(r.Context())
	switch r.Method {
	case http.MethodGet:
		doc, err := ctrl.svc.Items.AttachmentPath(r.Context(), ctx.GID, attachmentID)
		if err != nil {
			log.Err(err).Msg("failed to get attachment path")
			return validate.NewRequestError(err, http.StatusInternalServerError)
		}

		bucket, err := blob.OpenBucket(ctx, ctrl.repo.Attachments.GetConnString())
		if err != nil {
			log.Err(err).Msg("failed to open bucket")
			return validate.NewRequestError(err, http.StatusInternalServerError)
		}
		file, err := bucket.NewReader(ctx, doc.Path, nil)
		if err != nil {
			log.Err(err).Msg("failed to open file")
			return validate.NewRequestError(err, http.StatusInternalServerError)
		}
		defer func(file *blob.Reader) {
			err := file.Close()
			if err != nil {
				log.Err(err).Msg("failed to close file")
			}
		}(file)
		defer func(bucket *blob.Bucket) {
			err := bucket.Close()
			if err != nil {
				log.Err(err).Msg("failed to close bucket")
			}
		}(bucket)

		// Set the Content-Disposition header for RFC6266 compliance
		disposition := "attachment; filename*=UTF-8''" + url.QueryEscape(doc.Title)
		w.Header().Set("Content-Disposition", disposition)
		http.ServeContent(w, r, doc.Title, doc.CreatedAt, file)
		return nil

	// Delete Attachment Handler
	case http.MethodDelete:
		err = ctrl.svc.Items.AttachmentDelete(r.Context(), ctx.GID, ID, attachmentID)
		if err != nil {
			log.Err(err).Msg("failed to delete attachment")
			return validate.NewRequestError(err, http.StatusInternalServerError)
		}

		return server.JSON(w, http.StatusNoContent, nil)

	// Update Attachment Handler
	case http.MethodPut:
		var attachment repo.ItemAttachmentUpdate
		err = server.Decode(r, &attachment)
		if err != nil {
			log.Err(err).Msg("failed to decode attachment")
			return validate.NewRequestError(err, http.StatusBadRequest)
		}

		attachment.ID = attachmentID
		val, err := ctrl.svc.Items.AttachmentUpdate(ctx, ctx.GID, ID, &attachment)
		if err != nil {
			log.Err(err).Msg("failed to update attachment")
			return validate.NewRequestError(err, http.StatusInternalServerError)
		}

		return server.JSON(w, http.StatusOK, val)
	}

	return nil
}
